
using namespace std;

#include "IncludesServidor.h"
#include "AnimadorElementos.h"
#include "ProductorJugadorX.h"
#include "ConsumidorRespuestas.h"
#include "ConsumidorAcciones.h"
#include "VerificadorTorneoVivo.h"
#include "ActualizadorJugadores.h"
#include "Config.h"
#include "Partida.h"
#include <ostream>
#include <sstream>
#include <istream>

/* Variables globales al programa */
bool fin_logico = CONST_FALSE;				// Variable para indicar el fin logico del programa.
char semShmName[50];						// Nombre del semaforo de la memoria compartida de este servidor partida.
int puerto;
int duracion;
int inmunidad;
bool finTorneo = CONST_FALSE;

t_partida partidaShm;						// Structura donde se deposita lo leido de la shm
Partida *juego;								// Partida en si misma, con los objetos acciones y demas.
Socket *conexion;								// Socket de conexion a los jugadores.
SharedMemory *shm;							// Puntero al objeto que administra los accesos a la shm con el torneo.
Semaforo* semShm;							// Puntero al semaforo que sincroniza el acceso a la shm con el torneo.
ProductorJugadorX 	 *lecturaJugador1;		// Hilo que recibe las acciones del jugador1
ProductorJugadorX 	 *lecturaJugador2;		// Hilo que recibe las acciones del jugador2
ConsumidorRespuestas *escrituraJugadores;	// Hilo que envia acciones a ambos jugadores.
ConsumidorAcciones	 *procesoAcciones;		// Hilo que ejecuta las acciones recibidas por los productores.
AnimadorElementos	 *animadorElementos;	// Hilo que genera el movimiento ficticio de los objetos no jugadores.
VerificadorTorneoVivo *daemon2;				// Contorla todo el tiempo que no muera el torneo.
ActualizadorJugadores *refreshRank;			// Actualiza el ranking de los jugadores.

//Funciones
/* Inicia la partida, pone a correr los threads de escucha y escritura y el ejecutor de acciones. */
bool iniciarPartida();
/* Funcion que inicia el servidor de torneo, conecta la shm y activa el semaforo, asi como crea los threads de atencion, y demas...*/
bool iniciarServidorPartida();
/* Se encarga de liberar la memoria compartida, semaforos y cualquier recurso que pueda dejar tomado por error. */
void liberarRecursos(bool hayError);
/* Handler que toma las señal de finalización y la trasmite al thread. */
void signalHandler(int signal);
/* Toma el archivo de configuracion y setea sus parametros en el servidor, tambien los valida. */
bool configuracionesGenerales();
/* Avisa a los jugadores el fin del torneo enviandole la accion indicada */
void notificarFinTorneo(bool hayError);

//Main
int main(int argc, char **argv){
	//Me attacho a las señales de finalización.
	signal(SENIAL_FIN_DAEMON,signalHandler);
	signal(SENIAL_FIN_TORNEO,signalHandler);
	signal(SENIAL_FIN_TORNEO_ERROR,signalHandler);
	signal(SIGURG, signalHandler);
	signal(SIGABRT, signalHandler);
	signal(SIGFPE, signalHandler);
	signal(SIGILL, signalHandler);
	signal(SIGCLD, signalHandler);
	signal(SIGQUIT, signalHandler);
	signal(SIGIO, signalHandler);

	/* Toma el archivo de configuracion y setea el nombre de semaforos y parametros del juego. */
	if(!configuracionesGenerales()){
		liberarRecursos(CONST_FALSE);
		return CONST_UNO;
	}

	/* Inicia todos los servicios del servidor partida, pero sin arrancar la partida en si misma. */
	if(!iniciarServidorPartida()){
		liberarRecursos(CONST_FALSE);
		return CONST_UNO;
	}

    /* Iniciamos la partida */
	if(!iniciarPartida()){
		liberarRecursos(CONST_FALSE);
		return CONST_UNO;
	}

    /* Mientras la partida este activa voy a seguir loopeando y escuchando señales de daemon o torneo. */
    while(juego->isActivo() && !juego->isFinTorneo()){
    	usleep(SPEED_ANIM * CONST_CINCO);
    }

    /* Logueo */
    cout<<"[FIN] - La partida a finalizado. "<<endl;

    /* Si termino el torneo, envio la accion a los jugadores */
    if(juego->isFinTorneo() || finTorneo)
    	notificarFinTorneo(CONST_FALSE);

    /* Libero todos los recursos del servidor */
    liberarRecursos(CONST_FALSE);

    /* Salgo */
	return 0;
}


/* Se encarga de liberar la memoria compartida, semaforos y cualquier recurso que pueda dejar tomado por error. */
void liberarRecursos(bool hayError){
	//TODO: Ver si falta liberar algo.

	/* Si hubo error cierro todo yo */
	if(hayError){
		/* Desconecto la memoria compartida  y su semaforo */
		shm->cerrarShm();
		semShm->Liberar();
		semShm->Eliminar();

		/* Libero las conexiones a los jugadores */
		conexion->closeSocket(partidaShm.jugador1.socket_id);
		conexion->closeSocket(partidaShm.jugador2.socket_id);
	}

    /* Mato los threads */
	pthread_kill(escrituraJugadores->get_id(),	SIGURG);
	pthread_kill(procesoAcciones->get_id(),		SIGABRT);
	pthread_kill(lecturaJugador1->get_id(),		SIGFPE);
	pthread_kill(lecturaJugador2->get_id(),		SIGILL);
	pthread_kill(animadorElementos->get_id(),	SIGCLD);
	pthread_kill(daemon2->get_id(), 			SIGQUIT);
	pthread_kill(refreshRank->get_id(),			SIGIO);
}

/* Notifica a los jugadores que el torneo termino de forma inesperada o no */
void notificarFinTorneo(bool hayError){
	/* Genero la accion para que los clientes finalicen el torneo */
	t_accion a;
	a.id_accion = ACC_FIN_TORNEO;

	/* Señal que indica si el fin es con error o sin error */
	if(hayError)
		a.mov_x = CONST_UNO;
	else
		a.mov_x = CONST_CERO;

	/* Envio el fin a los jugadores */
	conexion->write((void *)&a, sizeof(a), Partida::getInstance()->getPartida()->jugador1.socket_id);
	conexion->write((void *)&a, sizeof(a), Partida::getInstance()->getPartida()->jugador2.socket_id);
}

/* Handler que toma las señal de finalización y la trasmite al thread. */
void signalHandler(int signal){
	//TODO: Manejar señales del DAEMON y de su propio thread.
	switch(signal){
		case SENIAL_FIN_DAEMON:
			//TODO: Que hacer cuando el torneo me dice terminar.
			Partida::getInstance()->setFinTorneo(CONST_TRUE);
			notificarFinTorneo(CONST_TRUE);
	    	liberarRecursos(CONST_TRUE);
	    	cout << "[ERROR] - Se finaliza la partida de forma abrupta pero liberando recursos usados." << endl;
	    	exit(1);
			break;

		case SENIAL_FIN_TORNEO_ERROR:
			//TODO: Que hacer cuando el torneo me dice terminar.
			Partida::getInstance()->setFinTorneo(CONST_TRUE);
			notificarFinTorneo(CONST_TRUE);
	    	liberarRecursos(CONST_TRUE);
	    	cout << "[ERROR] - Se finaliza la partida de forma abrupta pero liberando recursos usados." << endl;
	    	exit(1);
			break;

		case SENIAL_FIN_TORNEO:
			//TODO: Que hacer cuando el torneo me dice terminar.
			finTorneo = CONST_TRUE;
			break;
	}
}

/* Toma el archivo de configuracion y setea sus parametros en el servidor, tambien los valida. */
bool configuracionesGenerales(){
	/* Creo el nombre del semaforo para la shm */
	ostringstream ss;
	ss << "sem" << getpid();
	strcpy(semShmName, ss.str().c_str());

	/* Tomo los parametros de configuracion */
	inmunidad = 5;

	return CONST_TRUE;
}

/* Funcion que inicia el servidor de torneo, conecta la shm y activa el semaforo, asi como crea los threads de atencion, y demas...*/
bool iniciarServidorPartida(){
	/* Borro la pantalla al iniciar el Servidor. */
    if(system("clear"));
    cout << "[INFO] - Iniciando servidor de Partida - " << time(NULL) << endl;

    // Recibo la key para la shared memory
    key_t key = (key_t)getpid();

    // instancio la shm de lectura y escritura y el semaforo para su uso exclusivo
    shm = new SharedMemory(key);
	semShm = new Semaforo(1,semShmName);

    // 												Conecto a la memoria compartida
    if(!shm->conectarShm()){
        cout << "[ERROR] - No se pudo abrir la memoria compartida, se cierra el servidor" << endl;
        return CONST_FALSE;
    }else
    	cout << "[INFO] - Memoria compartida conectada. " << endl;

    // 												Leo los datos de la partida actual
    semShm->P();
    partidaShm = shm->leerDatos();
    semShm->V();

    /* Verifica que se pudo leer la primera shm */
    cout << "[INFO] - Datos de la partida, id:"<< partidaShm.partidaId << " jugador1: " << partidaShm.jugador1.nombre << " jugador2: "<< partidaShm.jugador2.nombre << endl;

    /* 											Genero los Threads de servicios pero sin arrancarlos. 							*/
    lecturaJugador1		= new ProductorJugadorX(DEATTACH);
    lecturaJugador2		= new ProductorJugadorX(DEATTACH);
    escrituraJugadores	= new ConsumidorRespuestas(DEATTACH);
    procesoAcciones   	= new ConsumidorAcciones(DEATTACH);
    animadorElementos   = new AnimadorElementos(DEATTACH);
    daemon2				= new VerificadorTorneoVivo(DEATTACH);
    refreshRank			= new ActualizadorJugadores(DEATTACH);

    /* 												Seteo los parametros necesarios para cada uno								*/
    /* Productor jugador 1 */
    lecturaJugador1->setNroJugador(CONST_UNO);
    lecturaJugador1->setConexion(conexion);

    /* Productor jugador 2 */
    lecturaJugador2->setNroJugador(CONST_DOS);
    lecturaJugador2->setConexion(conexion);

    /* Consumidor de respuestas */
    escrituraJugadores->setJugador1(partidaShm.jugador1.socket_id);
    escrituraJugadores->setJugador2(partidaShm.jugador2.socket_id);
    escrituraJugadores->setConexion(conexion);

    /* Inicio el daemon antes que todo */
    daemon2->start();

    return CONST_TRUE;
}

/* Inicia la partida, pone a correr los threads de escucha y escritura y el ejecutor de acciones. */
bool iniciarPartida(){
    juego = Partida::getInstance();
    juego->setShm(shm);
    juego->setSemShm(semShm);
    juego->setPartida(&partidaShm);

    /* Si estan disponibles ambos jugadores comienzo la partida */
    if(partidaShm.jugador1.conectado || partidaShm.jugador2.conectado){
    	/* Inicio la partida */
    	juego->iniciarPartida(conexion);

        /* Lanzo los 5 threads de atencion y ejecucion de acciones */
    	escrituraJugadores->start();
    	procesoAcciones->start();
    	lecturaJugador1->start();
    	lecturaJugador2->start();
    	animadorElementos->start();
    	refreshRank->start();

    }else{
    	/*Sino termino la partida aca nomas */
    	fin_logico = false;

    	/* Logueo */
    	cout << "[ERROR] - No se pudo iniciar la partida porque ambos jugadores no estan conectados." << endl;

    	return CONST_FALSE;
    }

    /* Logueo */
    cout << "[INFO] - Inicia la partida  " << getpid() << endl;

    return CONST_TRUE;
}
